所有的数据均在计算机内部以位序列的形式表示。每一位可以为0或1。在大多数系统中,8位构成一个字节(byte),字节是字符型变量的标准存储单位。其他数据类型以更多的字节表达。位运算符用来操纵整形操作数中的位。unsigned类型的整数通常使用位运算符来处理。
在C语言中,可以单独操控变量中的位。读者可能好奇,竟然有人想这样做。有时必须单独操控位,而且非常有用。例如,通常向硬件设备发送一两个字节来控制这些设备,其中的每个位(bit)都有特定的含义。另外,与文件相关的操作信息经常被储存,通过使用特定位表明特定项。许多压缩和加密操作都是直接处理单独的位。高级语言一般不会处理这些级别的细节,C在提供高级语言使得的同时,还能在为汇编语言所保留的级别上工作,这使其成为编写设备驱动程序和嵌入式代码的首选语言。
许多压缩和加密操作都是直接处理单独的位,在硬件控制中,可以用位来表示特定的信息。
在实际的程序设计中,有时需要存储少量的信息,这些信息并不需要占用一个完整的字节,只需占用几个或一个二进制位。例如,在存入一个标志时,只有0和1两种状态,用一个二进制即可。如果给其分配一个字节的空间,便浪费了存储空间。因此,C++引入了位域这一数据类型。
C有两种访问位的方法:按位运算符和在结构中创建位字段。
位运算在编程硬件设备中非常有用,因为设备的状态经常表示为一系列单独的标志(也就是字节的每个位可以表示设备各个方面的状态),在需要把一组一关标志装入单个变量中时,按位运算符也非常有用。
逻辑运算符和按位运算符之间的差别在于,按位运算符返回的并非布尔值,而是对操作数对应位执行指定运算的结果。C++让您能够执行按位NOT、OR、AND和XOR运算,它们分别使用~取反、使用|对相应位执行OR运算、使用&对相应位执行AND运算、使用^对相应位执行XOR运算。其中后三个运算符对变量与选择的数字(通常是位掩码)执行相应的运算。
在整数的每位都表示特定标记的状态时,有些按位运算很有用,例如,32位的整数可用于表示32个布尔标记。
位运算经常被用来创建、处理以及读取标志位序列——一种类似二进制的变量。虽然可以使用变量代替标志位序列,但是这样可以节省内存(1/32)。
与运算:
1 清零:
2 保留某些位:如想保留00010010,如想保留1、3、5、7只,只需执行00010010&1010101
在开发应用程序时,有时需要在一个字节中表示多项内容。例如,在描述IP协议的首部时,其首部长度占4位(bit),版本号占4位。而程序最小的空间分配单位是一个字节,即8位。在定义描述IP协议首部的结构体时,该如何实现呢? C/C++语言提供了位域,允许用户单独访问一位数据。
在计算机中,数据都是以二进制位(bit)表示,以字节(Byte)为最小单位进行存储。一个字节分为8位,每一位可以表示一个二进制数0或1。为了能够对一个字节中的某一位或几位进行操作,C++提供了6种位运算符。
其目的是用一个字节来表示更多的信息。如年份就可以使用一个字节来表示更多的信息,如用一个位来表示是31天还是30天?在程序中使用与运算可以进行清零操作。例如,将某个字节数据清零。可以将该字节的数据与0进行与运算,其结果将为0。因为任何数与0进行与运算,结果必然为0。此外,与运算还可以将一个字节中的某些位保留下来。例如,对于二进制数00001010,想要保留其1、3、5、7位,则可以将该二进制数与“01010101”二进制(该二进制数1、3、5、7位为1,其他位为0)进行与运算。
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如,“真”或“假”用0或1表示,只需1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。
怎样向一个字节中的一个或几个二进制位赋值和改变它的值呢?可以用以下两种方法:
可以人为地将一个整型变量data分为几部分。
但是用这种方法给一个字节中某几位赋值太麻烦。可以位段结构体的方法。
(2)位段
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域” ( bit field) 。利用位段能够用较少的位数存储数据。
(1)位段成员的类型必须指定为unsigned或int类型。
(2) 若某一位段要从另一个字开始存放,可用以下形式定义:
unsigned a:1;
unsigned b:2;一个存储单元
unsigned:0;
unsigned c:3;另一存储单元
a、b、c应连续存放在一个存储单元中,由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放。因此,只将a、b存储在一个存储单元中,c另存在下一个单元(“存储单元”可能是一个字节,也可能是2个字节,视不同的编译系统而异)。
(3) 一个位段必须存储在同一存储单元中,不能跨两个单元。如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。
(4) 可以定义无名位段。
(5) 位段的长度不能大于存储单元的长度,也不能定义位段数组。
(6) 位段可以用整型格式符输出。
(7) 位段可以在数值表达式中引用,它会被系统自动地转换成整型数。
算术右移:空出来的高位用符号位来填充;
逻辑右移:空出来的高位用0来填充;
结构或联合的成员也可以是位字段。位字段是一个由具有特定数量的位组成的整数变量。如果连续声明多个小的位字段,编译器会将它们合成一个机器字(word)。这使得小单元信息具有带回紧凑的存储方式。当然,也可以使用位运算符来独立处理特定位,但是位字段允许我们利用名称来处理位,类似于结构或联合的成员。
位字段的声明多了一个宽度的定义,如:
struct Date{//用一个字的空间存储一个日期 unsigned int month:4; unsigned int day:4; signed int year:22; _bool isDst:1; //如果是夏令时 };
位字段不能使用&来取址,也不能对位字段进行位运算。
高位左移后溢出,舍弃。(按数据类型长度)
右移运算符是a>>2表示将a的各二进制位右移2位,移到右端的低位被舍弃,对无符号数,高位补0。
a=1001011111101101 (用二进制形式表示,即使是十进制,内存中存储的本质都是二进制,所以也要时刻当做二进制看待)
a>>1: 0100101111110110 (逻辑右移时)
a>>1: 1100101111110110 (算术右移时)
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如,“真”或“假”用0或1表示,只需1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。
对一个数字进行取反运算结果是多少,还与该类型是有符号还是无符号,占用的字节大小等有关.
int a = 10;
printf("按位异或的定位取反功能%d\n按位取反%d\n", a ^ 0xFF, ~a);
按位异或的定位取反功能245
按位取反-11
0xFF 这个是只有低8位为1,高位全是0。
整型10 是低4位为 1010,高位全为0,且一般来说是占用32位。
你应该明白了吧,区别就在于高位的行为不一样。
取反是将a高位的0都变成1,这样最高位的符号位也变成1了,所以最终值是负的。
而异或后,a的后8位都取反了,但前24位保持不变。这样值就是245.
每当在使用整数之时都会将其想象成二进制的存在,而不是十进制。原因在于,这是程序的本质所在,稍有研究编译器工作原理的都会发现,在编译器处理乘法乃至除法的时候,优秀的编译器总会想方设法的加快程序的速度,毫无疑问在所有运算中移位运算是最快速的"乘法"以及"除法": 1<<2 == 4 ,8>>2 == 2
基本数据类型及其复合类型以字节为单位进行处理,位运算以位为单位进行处理。
整数与0异或为本身。与本身异或为0,即本身的奇数次异或还为本身,偶数次异或为0
int func(x) // 返回x的二进制位中1的位数 { int countx =0; while(x) { countx ++; x = x&(x-1); } return countx; }
When individual bits of an object are used as Boolean values, the bits are called bit flags.
Given a sequence of bits, we typically number the bits from right to left, starting with 0 (not 1). Each number denotes a bit position.
76543210 Bit position
00000101 Bit sequence
Given the bit sequence 0000 0101, the bits that in position 0 and 2 have value 1, and the other bits have value 0.
0011 << 3 is 1000
Note that in the third case, we shifted a bit off the end of the number! Bits that are shifted off the end of the binary number are lost forever.
constexpr unsigned char mask0{ 1 << 0 }; // 0000 0001 constexpr unsigned char mask1{ 1 << 1 }; // 0000 0010 constexpr unsigned char mask2{ 1 << 2 }; // 0000 0100 constexpr unsigned char mask3{ 1 << 3 }; // 0000 1000 constexpr unsigned char mask4{ 1 << 4 }; // 0001 0000 constexpr unsigned char mask5{ 1 << 5 }; // 0010 0000 constexpr unsigned char mask6{ 1 << 6 }; // 0100 0000 constexpr unsigned char mask7{ 1 << 7 }; // 1000 0000
if you defined the function using bit flags like this:
void someFunction(std::bitset<32> options);
Then you could use bit flags to pass in only the options you wanted:
someFunction(option10 | option32);
To query bit states, we use bitwise AND:
if (flags & option4) ... // if option4 is set, do something
To set bits (turn on), we use bitwise OR:
flags |= option4; // turn option 4 on. flags |= (option4 | option5); // turn options 4 and 5 on.
To clear bits (turn off), we use bitwise AND with bitwise NOT:
flags &= ~option4; // turn option 4 off flags &= ~(option4 | option5); // turn options 4 and 5 off
To flip bit states, we use bitwise XOR:
flags ^= option4; // flip option4 from on to off, or vice versa flags ^= (option4 | option5); // flip options 4 and 5